讓 Agent 能協調、組合並穩定使用多個工具(Multi-Tool Coordination),是將簡單的問答機器人轉變為複雜自動化系統的關鍵。
當工具數量從 1 個增加到 5 個、甚至幾十個時,Agent 會面臨三大挑戰:
要實現穩定的多工具 Agent,系統架構需要具備:
role: "tool") 妥善保存於對話歷史中,讓模型能進行 cross-tool 的上下文推理。 ┌─── Tool 1 (搜尋 API) ───┐
[LLM] ──發出多個 ToolCall ──┼─── Tool 2 (天氣 API) ───┼──> [App 執行並彙整] ──> [LLM 總結]
└─── Tool 3 (計算 API) ───┘
我們基於前面建構的 ToolRegistry,撰寫一個能夠靈活處理多工具相依性與並行呼叫的 Agent Engine。
import json
from openai import OpenAI
from typing import List, Dict, Any
# 假設已載入前面設計的 ToolRegistry 類別
# registry = ToolRegistry()
class MultiToolAgent:
def __init__(self, model_name: str, registry):
self.client = OpenAI()
self.model_name = model_name
self.registry = registry
def run(self, user_goal: str, max_steps: int = 10) -> str:
"""
執行 Agent Loop,允許多步驟與多工具組合執行
"""
messages: List[Dict[str, Any]] = [
{
"role": "system",
"content": (
"你是一個高效率的 AI 助理。你可以組合使用多個工具來達成使用者的目標。"
"如果多個工具之間沒有依賴關係,你可以一次發出多個工具呼叫以提高效率。"
)
},
{"role": "user", "content": user_goal}
]
print(f"🎯 任務目標: {user_goal}\n" + "="*50)
for step in range(1, max_steps + 1):
print(f"\n🔄 [Step {step}] Agent 思考中...")
# 1. 呼叫 LLM 進行思考 (Reasoning)
response = self.client.chat.completions.create(
model=self.model_name,
messages=messages,
tools=self.registry.get_openai_tools(),
tool_choice="auto"
)
response_message = response.choices[0].message
messages.append(response_message) # 記錄 LLM 的思考/發出的 Call
tool_calls = response_message.tool_calls
# 2. 終止條件:LLM 評估不需要再呼叫工具,輸出最終答案
if not tool_calls:
print(f"\n✅ 任務完成!最終回答:\n{response_message.content}")
return response_message.content
# 3. 執行工具 (Acting Phase) - 支援單輪多工具併發
print(f"🛠️ 本輪發出 {len(tool_calls)} 個工具呼叫:")
for tool_call in tool_calls:
tool_name = tool_call.function.name
args_str = tool_call.function.arguments
tool_id = tool_call.id
print(f" ├─ 呼叫: {tool_name} | 參數: {args_str}")
# 透過 Registry 派發執行
observation = self.registry.dispatch(tool_name, args_str)
print(f" └─ 觀察 (Observation): {observation[:80]}..." if len(observation) > 80 else f" └─ 觀察 (Observation): {observation}")
# 將每個工具執行結果放入 Context,並用 tool_call_id 精確對應
messages.append({
"role": "tool",
"tool_call_id": tool_id,
"name": tool_name,
"content": observation
})
return "⚠️ 超過最大步驟上限,任務中斷。"
我們為 Registry 註冊三個功能各異的工具,模擬「資料檢索 $\rightarrow$ 數據計算 $\rightarrow$ 發送報告」的工作流:
registry = ToolRegistry()
@registry.register(description="根據使用者名稱查詢其在系統中的消費記錄金額列表")
def get_user_expenses(username: str) -> list:
mock_db = {
"alice": [120, 450, 3200, 880],
"bob": [50, 60]
}
return mock_db.get(username.lower(), [])
@registry.register(description="計算一組數字列表的平均值與總和")
def calculate_stats(numbers: list[float]) -> dict:
if not numbers:
return {"sum": 0, "avg": 0}
total = sum(numbers)
return {"sum": total, "avg": round(total / len(numbers), 2)}
@registry.register(description="發送通知報告給指定使用者")
def send_notification(username: str, message: str) -> str:
# 模擬通知 API
return f"通知已成功發送給 {username}:[{message}]"
agent = MultiToolAgent(model_name="gpt-4o-mini", registry=registry)
prompt = "請幫我查 Alice 的消費記錄,計算總花費與平均花費,最後發送通知告知 Alice 她這期的消費統計。"
agent.run(prompt)
get_user_expenses(username="Alice")。Observation: [120, 450, 3200, 880]
calculate_stats(numbers=[120, 450, 3200, 880])。Observation: {"sum": 4650, "avg": 1162.5}
send_notification(username="Alice", message="您本期總花費為 4650 元,平均每筆消費 1162.5 元。")。Observation: "通知已成功發送..."
當工具數量持續增加時,開發者會遇到以下問題與解決策略:
get_user_expenses 比 fetch_data 好。"YYYY-MM-DD")。get_expenses、search),Agent 可以全自主執行。send_email、transfer_money),在 Agent 決定 Call Tool 時,系統發出 Pause 訊號,跳出 UI 供使用者確認(Approve / Reject)後再繼續運作。